using System;
using System.Collections.Generic;
using System.Text;
using System.CodeDom;
using SharpMud.Text.Matching;

// Alot of hard thinking and work went into the regx's here. Honestly.
namespace SharpMud.Text.Dynamic.Processing
{

    /// <summary>
    /// Contains static methods that parse a Dynamic Text Body into INugget's, which
    /// can then be converted into System.CodeDom.CodeObject's.
    /// </summary>
    public class DynamicTextProcessor
    {
        private DynamicTextProcessor()
        {
        }

        #region RegEx StringMatcher's
        const string MetacommandNuggetMatcher_regx = @"<%@[\s]*[a-zA-Z]{1,1}[a-zA-Z0-9]*[\s]*[" + MetacommandParameterMatcher_regx + @"]*[\s]*%>";
        public static StringMatcher MetacommandNuggetMatcher
        {
            get
            {
                return StringMatcher.MakeRegexMatcher(MetacommandNuggetMatcher_regx);
            }
        }

        //REMEMBER: double double-quotes ( "" ) in the @"" string format is an escape for a single double-quote in the resulting string
        //const string MetacommandParameterMatcher_regx = @"[\s]{1,}[a-zA-Z]{1,}[a-zA-Z0-9]*[\s]*[=]{1,1}[\s]*[""]{1,1}[\S\s]*[""]{1,1}[\s]";
        const string MetacommandParameterMatcher_regx = @"[\s]{1,}[a-zA-Z]{1,}[a-zA-Z0-9]*[\s]*[=]{1,1}[\s]*[""]{1,1}[\S\s]*?[""]{1,1}";
        public static StringMatcher MetacommandParameterMatcher
        {
            get
            {
                return StringMatcher.MakeRegexMatcher(MetacommandParameterMatcher_regx);
            }
        }

        //const string CodeNuggetMatcher_regx = @"<%[\s]{1,}[\s\S]*[\s]*%>";
        const string CodeNuggetMatcher_regx = @"<%[\s]{1,}[\s\S]*?[\s]*%>";
        public static StringMatcher CodeNuggetMatcher
        {
            get
            {
                return StringMatcher.MakeRegexMatcher(CodeNuggetMatcher_regx);
            }
        }

        //const string TextExpressionNuggetMatcher_regx = @"<%[\s]*[=]{1,1}[s]*[\s\S]*[\s]*%>";
        const string TextExpressionNuggetMatcher_regx = @"<%[\s]*[=]{1,1}[s]*[\s\S]*?[\s]*%>";
        public static StringMatcher TextExpressionNuggetMatcher
        {
            get
            {
                return StringMatcher.MakeRegexMatcher(TextExpressionNuggetMatcher_regx);
            }
        }
        #endregion

        #region Top-Level Processing
        /// <summary>
        /// This function effectively parses a Dynamic Text Body into nuggets that are then converted to a IProcessedDynamicTextBody, which is a DTB ready to be compiled
        /// </summary>
        public static IProcessedDynamicTextBody Process(ISimpleDynamicTextBody dynamicTextSource, System.CodeDom.Compiler.CodeDomProvider codeDomProvider)
        {
            INugget[] nuggets = Parse(dynamicTextSource, codeDomProvider);
            HashWrapper sourceHash = new HashWrapper(typeof(System.Security.Cryptography.SHA512Managed), System.Text.Encoding.Default.GetBytes(dynamicTextSource.Contents));
            return new ProcessedDynamicTextBody(nuggets, sourceHash, codeDomProvider);
        }

        private static INugget[] Parse(ISimpleDynamicTextBody dynamicTextSource, System.CodeDom.Compiler.CodeDomProvider codeDomProvider)
        {
            string source = dynamicTextSource.Contents;
            int index = 0;
            List<INugget> nuggets = new List<INugget>();
            while (source.Length > 0)
            {
                INugget newNugget = PopNextNugget(ref source, ref index, dynamicTextSource, codeDomProvider);
                nuggets.Add(newNugget);
            }
            return nuggets.ToArray();
        }

        private static INugget PopNextNugget(ref string remainingSource, ref int beginningIndex, ISimpleDynamicTextBody origDynamicTextSource, System.CodeDom.Compiler.CodeDomProvider codeDomProvider)
        {
            //Try and find the first specific type of nugget (other than raw text)
            List<MatchInfo> currentMatches = new List<MatchInfo>();
            currentMatches.Add(new MatchInfo(DynamicTextProcessor.CodeNuggetMatcher.Match(remainingSource), typeof(Processing.RawCodeNugget)));
            currentMatches.Add(new MatchInfo(DynamicTextProcessor.MetacommandNuggetMatcher.Match(remainingSource), typeof(Processing.MetacommandNugget)));
            currentMatches.Add(new MatchInfo(DynamicTextProcessor.TextExpressionNuggetMatcher.Match(remainingSource), typeof(Processing.TextExpressionNugget)));
            //Sort them and put the earliest occurence at top of list
            currentMatches.RemoveAll(new Predicate<MatchInfo>(MatchInfo.IsUselessMatchInfo));
            currentMatches.Sort(new Comparison<MatchInfo>(MatchInfo.CompareMatchInfosAscending));
            

            if (currentMatches.Count == 0)
            { //If there were no matches, then the whole string is raw text
                string exactText = (string)remainingSource.Clone();
                remainingSource = String.Empty;

                CodeLinePragma codeLinePragma = new CodeLinePragma(origDynamicTextSource.FileName, WhichLine(origDynamicTextSource.Contents, beginningIndex));
                beginningIndex = beginningIndex + exactText.Length;

                return new RawTextNugget(exactText, codeLinePragma);
            }
            else if (currentMatches[0].Match.Index > 0)
            { //Since the earliest match isn't at the beginning of the string, we need to use the beginning text up first
                string exactText = remainingSource.Substring(0, currentMatches[0].Match.Index);
                remainingSource = remainingSource.Substring(currentMatches[0].Match.Index);

                CodeLinePragma codeLinePragma = new CodeLinePragma(origDynamicTextSource.FileName, WhichLine(origDynamicTextSource.Contents, beginningIndex));
                beginningIndex = beginningIndex + exactText.Length;

                return new RawTextNugget(exactText, codeLinePragma);
            }
            else
            { //We have a match that we can use right here and now
                string exactText = currentMatches[0].Match.Value;
                remainingSource = remainingSource.Substring(currentMatches[0].Match.Index + currentMatches[0].Match.Length);

                CodeLinePragma codeLinePragma = new CodeLinePragma(origDynamicTextSource.FileName, WhichLine(origDynamicTextSource.Contents, beginningIndex));
                beginningIndex = beginningIndex + exactText.Length;

                INugget newNugget = null;
                if (currentMatches[0].NuggetType == typeof(RawCodeNugget))
                {
                    newNugget = ParseCodeNugget(origDynamicTextSource.CodeDomProvider, exactText, codeLinePragma);
                }
                else if (currentMatches[0].NuggetType == typeof(MetacommandNugget))
                {
                    newNugget = ParseMetacommandNugget(origDynamicTextSource.CodeDomProvider, exactText, codeLinePragma);
                }
                else if (currentMatches[0].NuggetType == typeof(TextExpressionNugget))
                {
                    newNugget = ParseTextExpressionNugget(origDynamicTextSource.CodeDomProvider, exactText, codeLinePragma);
                }
                else
                {
                    throw new InvalidOperationException();
                }
                return newNugget;
            }
        }
        
        #endregion

        #region Parse Found Nuggets
        private static INugget ParseTextExpressionNugget(System.CodeDom.Compiler.CodeDomProvider codeDomProvider, string exactText, CodeLinePragma codeLinePragma)
        {
            string s = GetBracketedContents(exactText);
            //Chop off the beginning equals "="
            s = s.Substring(s.IndexOf("=") + 1);
            //Trim it up
            s = s.Trim();
            //Return the remaining stream as a literal code expression
            if (codeDomProvider != null)
                return new TextExpressionNugget(s, codeDomProvider.GetType(), codeLinePragma);
            else
                return new TextExpressionNugget(s, null, codeLinePragma);
        }

        private static INugget ParseCodeNugget(System.CodeDom.Compiler.CodeDomProvider codeDomProvider, string exactText, CodeLinePragma codeLinePragma)
        {
            string s = GetBracketedContents(exactText);
            //Trim it up
            s = s.Trim();
            //Return the remaining stream as a literal code expression
            if (codeDomProvider != null)
                return new RawCodeNugget(codeDomProvider.GetType(), s, codeLinePragma);
            else
                return new RawCodeNugget(null, s, codeLinePragma);
        }

        private static INugget ParseMetacommandNugget(System.CodeDom.Compiler.CodeDomProvider codeDomProvider, string exactText, CodeLinePragma codeLinePragma)
        {
            string s = GetBracketedContents(exactText);
            //Chop off begining @ symbol
            s = s.Substring(s.IndexOf("@") + 1).Trim();

            List<IStringMatcherMatch> unparsedParameters = new List<IStringMatcherMatch>();
            unparsedParameters.AddRange(DynamicTextProcessor.MetacommandParameterMatcher.Matches(s));
            //unparsedParameters.Sort(new Comparer<IStringMatcherMatch>(MatchInfo.CompareMatchesAscending));
            IList<KeyValuePair<string, string>> parsedParameters = new List<KeyValuePair<string, string>>();
            char sep = '=';
            foreach (IStringMatcherMatch match in unparsedParameters)
            {
                string[] keyValuePair = new string[] {"",""};
                keyValuePair[0] = match.Value.Substring(0,match.Value.IndexOf("=")).Trim();
                keyValuePair[1] = match.Value.Substring(keyValuePair[0].Length + 1);
                keyValuePair[1] = GetQuotedContents(keyValuePair[1]);
                parsedParameters.Add(new KeyValuePair<string,string>(keyValuePair[0], keyValuePair[1]));
            }
            if (unparsedParameters.Count == 0)
            {
                return new MetacommandNugget(s.Trim(), parsedParameters, codeLinePragma);
            }
            else
            {
                int firstParameterIndex = unparsedParameters[0].Index;
                return new MetacommandNugget(s.Substring(0, firstParameterIndex).Trim(), parsedParameters, codeLinePragma);
            }
        }
        #endregion

        #region Miscellaneous Helper Methods
        /// <summary>
        /// Scans through the given text and counts the lines up until the given character index
        /// </summary>
        private static int WhichLine(string text, int characterIndex)
        {
            //    \r\n    or just    \n    or just     \r

            int curLine = 1;
            bool pendingLinefeed = false;
            for(int x=0;((x<text.Length)&&(x<=characterIndex));x++)
            {
                if (text[x] == '\r')
                {
                    pendingLinefeed = true;
                }
                else if (text[x] == '\n')
                {
                    curLine = curLine + 1;
                    pendingLinefeed = false;
                }
                else if (pendingLinefeed)
                {
                    curLine = curLine + 1;
                    pendingLinefeed = false;
                }
            }
            return curLine;
        }

        /// <summary>
        /// Remove "<%" and "%>" from text
        /// </summary>
        private static string GetBracketedContents(string bracketedDynamicTextDevice)
        {
            string s = bracketedDynamicTextDevice;
            //Chop off beginning "<%"
            s = s.Substring(s.IndexOf("<%")+2);
            //Chop off trailing "%>"
            s = s.Substring(0, s.IndexOf("%>"));
            return s;
        }

        /// <summary>
        /// Gets the contents of a string between a pair of double-quotes
        /// </summary>
        private static string GetQuotedContents(string quotedString)
        {
            string s = quotedString.Trim();
            if (s.Length <= 2)
                return String.Empty;
            if (s.IndexOf("\"") == s.LastIndexOf("\""))
                return null;
            int i1 = s.IndexOf("\"");
            int i2 = s.IndexOf("\"", i1 + 1);
            return s.Substring(i1 + 1, i2-(i1+1));
        }
        #endregion
    }
}
